import * as THREE from "../../../modules/three/three.module.js";
import {gltfLoader, randBetween} from "../util/Utils.js";
import {arrAsubB, removeArrayItem, uvBufferAttributeToVectors} from "../util/StaticMethods.js";
import {OBB} from "../../../modules/three/jsm/math/OBB.js";
import {DebrisItem, getRaycasterFromBullet, StatusEnum} from "./SimpleBreakableEntity.js";
import {d2r} from "../lib/CommonConsts.js";

const modelBreakableUrls = [
  {// 花瓶
    whole: '../public/3d/models/A1.glb',
    broken: '../public/3d/models/A2.glb',
  }
]
const missingDistance = 200
let bottleCount = 0
const _obb = new OBB()
const _v0 = new THREE.Vector3()
const _mat = new THREE.Matrix4()
function getObb(breakableEntity) {
  const box3 = breakableEntity.boundingBox
  _obb.fromBox3(box3)
  _obb.center.copy(breakableEntity.position)
  _mat.makeRotationFromQuaternion(breakableEntity.quaternion)
  _obb.rotation.setFromMatrix4(_mat)
  return _obb
}
const _box3 = new THREE.Box3()
const _box2 = new THREE.Box2()
const _v1 = new THREE.Vector3()
function reCenterMesh(mesh) {
  _box3.setFromBufferAttribute(mesh.geometry.attributes.position)
  _box3.getCenter(_v1)
  mesh.position.copy(_v1)
  _v1.negate()
  _mat.makeTranslation(_v1)
  mesh.geometry.applyMatrix4(_mat)
}

const textureLoader = new THREE.TextureLoader();
const textureEquirec = textureLoader.load( '../public/3d/pics/sphereBack.jpg'); // ../public/3d/pics/sphereBack.jpg in_house
textureEquirec.mapping = THREE.EquirectangularReflectionMapping;
// textureEquirec.mapping = THREE.EquirectangularRefractionMapping
textureEquirec.colorSpace = THREE.LinearSRGBColorSpace;

export class ModelBreakable {
  // 初始位置
  pos = new THREE.Vector3()
  // 初始速度
  velocity = new THREE.Vector3()
  // 自旋速度
  speedRot = new THREE.Euler()
}

export class ModelBreakableEntity extends THREE.Group {
  /**
   * @type {ModelBreakable}
   */
  vo
  /**
   * @type {THREE.Mesh|null}
   */
  modelMesh = null
  /**
   * @type {THREE.CubeTexture|null}
   */
  textureEnv
  localPos = new THREE.Vector3()
  /**
   * @type {THREE.Mesh[]}
   */
  debrisMeshes = []
  boundingBox = new THREE.Box3()
  size = new THREE.Vector3()
  hitNormal = new THREE.Vector3()
  status = StatusEnum.none
  dir = new THREE.Vector3(0, 1, 0)
  stay = 35

  constructor(breakable, textureEnv) {
    super();
    this.textureEnv = textureEnv
    this.position.copy(breakable.pos)
    this.rotation.x = Math.PI * 0.5;
    gltfLoader.load(modelBreakableUrls[0].whole, (gltf) => {
      // gltf.scene.rotation.x = Math.PI * 0.5;
      this.boundingBox.setFromObject(gltf.scene);
      this.boundingBox.getSize(this.size);
      this.add(gltf.scene);

      this.modelMesh = gltf.scene;
      this.modelMesh.traverse(it => {
        it.receiveShadow = false;
        it.castShadow = true;
        if (it.geometry) {
          // it.geometry.computeVertexNormals();
          reCenterMesh(it);
          this.localPos.copy(it.position)
          it.position.set(0, 0, 0)
          it.scale.set(2, 2, 2)
          // console.log('localPos, ', it.position.toArray())
        }
        if (it.material && textureEnv) {
          it.material = new THREE.MeshBasicMaterial({envMap: textureEnv}) // textureEquirec
          // it.material.map = null
          // it.material.envMap = textureEquirec
        }
      })
    })
    const debrisMeshes = this.debrisMeshes
    gltfLoader.load(modelBreakableUrls[0].broken, (gltf) => {
      const group = gltf.scene;
      group.traverse(it => {
        if (it.geometry) {
          it.geometry.computeVertexNormals();
          debrisMeshes.push(it);
          reCenterMesh(it);
        }
        if (it.material && textureEnv) {
          it.material = new THREE.MeshBasicMaterial({envMap: textureEnv}) // textureEquirec
        }
      });
    });
    this.vo = breakable
  }

  disposeBreakable(breakables) {
    this.removeFromParent()
    removeArrayItem(breakables, this)
  }
  hitTestWithBullet(bulletEntity) {
    const raycaster = getRaycasterFromBullet(bulletEntity)
    const obb = getObb(this)
    const intersectPoint = obb.intersectRay(raycaster.ray, _v0)
    if (!intersectPoint) {
      return false
    }
    bulletEntity.detachItem()
    this.hitNormal.copy(intersectPoint).sub(this.position).normalize()
    return true
  }
  hitTestWithDrone(aircraftEntity) {
    const obb = getObb(this)
    const obbDrone = aircraftEntity.getObb()
    return obb.intersectsOBB(obbDrone)
  }
  //被子弹击中后，产生的若干碎片
  breakToPieces(emitter) {
    const pos = this.localPos
    const worldPos = this.position
    this.debrisMeshes.forEach(it => {
      let dir
      if (it.position.equals(pos)) {
        dir = this.hitNormal.clone().negate()
      } else {
        dir = it.position.clone().sub(pos).normalize()
      }
      const amount = randBetween(0.1, 0.25)
      const amounts = []
      for (let i = 0; i <= 90; i += 1) {
        const a = amount * Math.cos(i * d2r)
        amounts.push(a)
      }
      it.isDebris = true
      it.position.add(worldPos)
      const debrisItem = new DebrisItem(it, dir, amounts)
      emitter.debrisFromBreakable.push(debrisItem)
      emitter.scene.add(it)
    })
    return this.debrisMeshes
  }
  tick(aircraft, emitter) {
    if (this.status !== StatusEnum.none) {
      this.stay--
      if (this.stay <= 0 || this.status === StatusEnum.missing || this.status === StatusEnum.disappearing) {
        this.disposeBreakable(emitter.targets)
      }
      return;
    }
    const r = this.vo.speedRot
    this.rotation.x += r.x
    this.rotation.y += r.y
    this.rotation.z += r.z

    const velocity = this.vo.velocity
    this.dir.copy(velocity)
    this.position.add(velocity)
    if (this.hitTestWithDrone(aircraft)) {
      this.status = StatusEnum.disappearing
      console.log('ModelBreakableEntity 命中无人机')
      this.breakToPieces(emitter)
      aircraft.collided(this.dir)
      return
    }
    if (this.position.distanceTo(aircraft.position) > missingDistance) {
      this.status = StatusEnum.missing
      return
    }
    const bullets = aircraft.bullets
    for (let i = 0; i < bullets.length; i++) {
      if (this.hitTestWithBullet(bullets[i])) {
        this.status = StatusEnum.breaking
        // console.log('SimpleBreakableEntity 被子弹命中')
        this.breakToPieces(emitter)
        this.disposeBreakable(emitter.targets)
        return
      }
    }
  }

}

export class ModelBreakableEmitter {
  /**
   * @type {ModelBreakableEntity[]}
   */
  targets = []
  pos = new THREE.Vector3(230, 300, 3)
  range = new THREE.Vector3(42, 28, 100)
  /**
   * @type {DebrisItem[]}
   */
  debrisFromBreakable = []
  scene

  constructor(scene) {
    this.scene = scene
  }
  tick4Debris() {
    const arr = this.debrisFromBreakable
    if (!arr.length) {
      return
    }
    const removing = []
    arr.forEach((debris, index) => {
      const updated = debris.updateDebris()
      if (!updated) {
        removing.push(debris)
        // console.log('remove debris, ', index)
        debris.mesh.removeFromParent()
      }
    })
    arrAsubB(arr, removing)
  }
  tick(scene3d) {
    const aircraft = scene3d.store.aircraft
    if (!aircraft) {
      return;
    }
    this.tick4Debris()
    const dist = this.pos.distanceTo(aircraft.position)
    if (dist > missingDistance) {
      return;
    }
    this.targets.forEach(it => {
      it.tick(aircraft, this)
    })
    if (this.targets.length >= 7) {
      return;
    }

    const vo = new ModelBreakable()
    vo.pos.x = this.pos.x + this.range.x * Math.random()
    vo.pos.y = this.pos.y + this.range.y * Math.random()
    const zVal = Math.max(aircraft.position.z - 2, 3)
    vo.pos.z = randBetween(zVal, aircraft.position.z + 5)
    vo.velocity = aircraft.position.clone().sub(vo.pos).normalize()
    vo.velocity.multiplyScalar(randBetween(0.05, 0.2))
    vo.speedRot.x = randBetween(0, 0.1)
    const aa = ~~(Math.random() * 5)
    if (aa === 0) {
      vo.speedRot.x = 0
    } else {
      vo.speedRot.y = randBetween(0, 0.05)
    }

    const textureEnv = bottleCount === 0 ? null : scene3d.getCubeTexture(bottleCount === 1)
    bottleCount++
    const entity = new ModelBreakableEntity(vo, textureEnv)
    this.scene.add(entity)
    this.targets.push(entity)
    bottleCount %= 3
  }
}
